# include "store.h"


Store * newStore () {
        Store *st = (Store *) malloc(sizeof(Store));
        if (!st) logMessage("data.store","store.c","newStore","Memória Insuficiente",CRITICAL);
        st->uces = NULL;
        st->mapUCEs = NULL;
        st->allocationGraph = NULL;
        st->commonStudents = NULL;
        st->nUCEs = 0;
        st->nonAllocatedStudents = NULL;
        st->uceInfo = NULL;
        st->unhappyStudents = NULL;
        st->studentList = NULL;
        return st;
}
void freeStore (Store *st) {
        if (st) {
                if (st->mapUCEs) free(st->mapUCEs);
                if (st->uces) free(st->uces);
                if (st->uceInfo) free(st->uceInfo);
                if (st->allocationGraph) freeGraph(st->allocationGraph);
                if (st->commonStudents) freeGraph(st->commonStudents);
                if (st->nonAllocatedStudents) freeLinkedList(st->nonAllocatedStudents);
        	free(st);
	}
}
int getUCEgap (Store *st, int uce) {
        if (!st) return 0;
        if (!st->uceInfo) return 0;
        if (!st->uceInfo[uce]) return 0;
        if (!st->uces) return 0;
        int n = st->uceInfo[uce]->maxStudents;
        int actual;
        if (!st->uces[uce]) actual = 0;
        else actual = arrayGetLength(st->uces[uce]);
        return (n-actual);
}
void removeEdge (Store *st, int uceA, int uceB) {
        if (!st) return;
        if (!st->allocationGraph) return;
        setEdgeValue(st->allocationGraph,uceA,uceB,CUTTED_EDGE);
}
int isRemovedEdge (Store *st, int uceA, int uceB) {
        if (!st) return 0;
        if (!st->allocationGraph) return 0;
        if (getEdgeValue(st->allocationGraph,uceA,uceB) == CUTTED_EDGE) return 1;
        return 0;
}
int getUCEIndice (Store *st, char *uce) {
        if (!st) return -1;
        if (!st->mapUCEs) return -1;
        int i;
        for (i=0;i<st->nUCEs;i++) if (!strcmp(st->mapUCEs[i],uce)) return i;
        return -1;
}
int uceExists (Store *st, char *uce) {
        return (getUCEIndice(st,uce) >= 0);
}
void addNonAllocated (Store *store, Student *st) {
        if (!store) return;
        if (!existsElem(store->nonAllocatedStudents,st,equalStudent))
                store->nonAllocatedStudents = insertElemHead(store->nonAllocatedStudents,st);
}
void fillInfo (Store *st, ChainingHashMap *uces, ArrayList *stLst) {
        if (!st) return;
        st->studentList = stLst;
        LinkedList *aux,*lst;
        int i = 0,n;
        lst = aux = keySetChaining(uces);
        n = listSize(lst);
        st->mapUCEs = (char **) malloc(sizeof(char *)*n);
        st->uces = (ArrayList **) malloc(sizeof(ArrayList *)*n);
        st->uceInfo = (UCE **) malloc(sizeof(UCE *)*n);
        st->nUCEs = n;
        st->commonStudents = newGraph(n);
        st->allocationGraph = newGraph(n);
        if (!st->mapUCEs || !st->uces || !st->uceInfo )
                logMessage("data.store","store.c","fillInfo","Memoria Insuficiente",CRITICAL);
        while (aux) {
                st->mapUCEs[i] = getData(aux);
                st->uceInfo[i] = getChaining(uces,st->mapUCEs[i]);
                st->uces[i] = NULL;
                aux = aux->next; i++;
        }
        freeLinkedList(lst);
}

int addStudentToUCE (Store *store, Student *st, int uce, int verify, int nonAllocated, int limites) {
	if (!store) return 0;
	if (!st) return 0;
	if (uce<0) return 0;
	int added = 1;
	if (limites) {
		if (getUCEgap(store,uce)>0) {
			if (!store->uces[uce]) store->uces[uce] = newArrayList(STUDENT_LIST_SIZE);
			arrayInsert(store->uces[uce],st);
		} else added = 0;
	} else {
		if (!store->uces[uce]) store->uces[uce] = newArrayList(STUDENT_LIST_SIZE);
		arrayInsert(store->uces[uce],st);
	}
	if (nonAllocated&&!added) {
		if (!existsElem(store->nonAllocatedStudents,st,equalStudent))
			store->nonAllocatedStudents = insertElemHead(store->nonAllocatedStudents,st);
	}
	if (added) {
		// verificar se está na lista de nao alocados
		while (existsElem(store->nonAllocatedStudents,st,equalStudent))
                	store->nonAllocatedStudents = removeElem(store->nonAllocatedStudents,st,equalStudent);
	}
	if (added&&verify) {
		// verificar se este aluno vai ficar feliz
		if (isFirstSelection(st,store->mapUCEs[uce])) {
			// ficou feliz!
			while (existsElem(store->unhappyStudents,st,equalStudent))
				store->unhappyStudents = removeElem(store->unhappyStudents,st,equalStudent);
		} else {
			if (!existsElem(store->unhappyStudents,st,equalStudent))
				store->unhappyStudents = insertElemHead(store->unhappyStudents,st);
		}
	}
	return added;
}
int removeStudentFromUCE (Store *st, Student *s, int uce) {
	// simplesmente vai remover e verificar se esta em alguma lista!
	do {
		arrayRemove(st->uces[uce],s,equalStudent);
	} while (arrayIndexOf(st->uces[uce],s,equalStudent)>=0);
	while (existsElem(st->unhappyStudents,s,equalStudent))
		st->unhappyStudents = removeElem(st->unhappyStudents,s,equalStudent);
	return 1;
}

int addStudent (Store *st, Student *s, int recursive, int addToNonAllocated, int limites) {
	// vai tentar colocar o aluno em qualquer lado!
	char *u,*uce = getNextSelection(s,NULL);
	int iB,iA = getUCEIndice(st,uce);
	if (iA<0) return 0;
	if (s->ucesWanted>st->nUCEs) return 0;
	int added = 0;
	int v;
	if (s->ucesWanted == 1) {
		added = addStudentToUCE(st,s,iA,1,addToNonAllocated,limites);
		uce = getNextSelection(s,uce);
		while (!added&&recursive&&uce) {
			iA = getUCEIndice(st,uce);
			added = addStudentToUCE(st,s,iA,1,addToNonAllocated,limites);
			uce = getNextSelection(s,uce);
		}
		if (added) s->uceA = iA;
		else s->uceA = -1;
	} else {
		// tenta adicionar a DUAS!
		u = getNextSelection(s,uce);	
		iB = getUCEIndice(st,u);
		if (!isRemovedEdge(st,iA,iB)) {
			// posso tentar!
			added = addStudentToUCE(st,s,iA,1,addToNonAllocated,limites);
			if (added) {
				added = addStudentToUCE(st,s,iB,1,addToNonAllocated,limites);
				if (!added) removeStudentFromUCE(st,s,iA);
			}
		}
		while (!added&&recursive&&uce) {
			u = getNextSelection(s,uce);
			while (!added&&u) {
				iB = getUCEIndice(st,u);
				if (!isRemovedEdge(st,iA,iB)) {
					added = addStudentToUCE(st,s,iA,1,addToNonAllocated,limites);
					if (added) {
						added = addStudentToUCE(st,s,iB,1,addToNonAllocated,limites);
						if (!added) removeStudentFromUCE(st,s,iA);
					}
				}
				u = getNextSelection(s,u);
			}
			uce = getNextSelection(s,uce);
			if (uce&&!added) iA = getUCEIndice(st,uce);
		}
		if (added) {
			// contar o peso!
			s->uceA = iA;
			s->uceB = iB;
			v = getEdgeValue(st->allocationGraph,iA,iB);
			setEdgeValue(st->allocationGraph,iA,iB,v+calcWeight(st->studentList,s));
			// adicionar um aluno ao nodo!
			v = getEdgeValue(st->commonStudents,iA,iB);
			setEdgeValue(st->commonStudents,iA,iB,v+1);
		} else {
			s->uceA = -1;
			s->uceB = -1;
		}
	}
	return added;
}
int removeStudent (Store *st, Student *s) {
	// remove o aluno da uce(s) à(s) qual(quais) está alocado
	int v;
	if (!s) return 0;
	if (s->ucesWanted == 1) {
		if (s->uceA == -1) return 0;// .. nao devia acontecer!
		removeStudentFromUCE(st,s,s->uceA);
		s->uceA = -1;
	} else {
		// remover das duas!
		if (s->uceA != -1 && s->uceB == -1) {
			printf("ERRO! Aluno nao vai ser removido do par\n");
			return 0;
		}
		if (s->uceA == -1 || s->uceB == -1) return 0;
		removeStudentFromUCE(st,s,s->uceA);
		removeStudentFromUCE(st,s,s->uceB);
		v = getEdgeValue(st->commonStudents,s->uceA,s->uceB);
		setEdgeValue(st->commonStudents,s->uceA,s->uceB,v-1);
		if (!isRemovedEdge(st,s->uceA,s->uceB)) {
			v = getEdgeValue(st->allocationGraph,s->uceA,s->uceB);
			setEdgeValue(st->allocationGraph,s->uceA,s->uceB,v-calcWeight(st->studentList,s));
		}
		s->uceA = -1;
		s->uceB = -1;
	}
	return 1;
}
static LinkedList * adicionarEmFalta (Store *st, LinkedList *lst) {
	int i; Selection *sel;
	for (i=0;i<st->nUCEs;i++) {
		sel = newSelection(strdup(st->mapUCEs[i]),0);
		if (!existsElem(lst,sel,equalSelection))
			lst = insertElemHead(lst,sel);
		else freeSelection(sel);
	}
	return lst;
}
void initialDistribuition (Store *st, int pro, int extra, int type) {
	// vai percorrer a lista de aluno e tentar alocar!
	ArrayList *lst = st->studentList;
	int i,n = arrayGetLength(lst);
	Student *s; int add;
	for (i=0;i<n;i++) {
		s = arrayGet(lst,i);
		s->selections = adicionarEmFalta(st,s->selections);
		s->selections = sortList(s->selections,compareSelection,1);
		add = 0;
		switch (s->type) {
			case NORMAL:
				add = 1;
				break;
			case EXTRA:
				if (extra) add = 1;
				break;
			case PROFESSIONAL:
				if (pro) add = 1;
				break;
		}
		if (add) {
			switch (type) {
				case 0:
					addStudent(st,s,0,1,0);
					break;
				case 1:
					addStudent(st,s,0,1,1);
					break;
				case 2:
					addStudent(st,s,1,1,1);
					break;
			}
		}
	}
}
Graph * getGraph (Store *st) {
	Graph *g = newGraph(st->allocationGraph->nodes);
	int i,j,v;
	for (i=1;i<g->nodes;i++) {
		for (j=0;j<i;j++) {
			if (!isRemovedEdge(st,i,j)) {
				v = getEdgeValue(st->allocationGraph,i,j);
				setEdgeValue(g,i,j,v);
			}
		}
	}
	return g;
}

void allocateRole (Store *st, StudentType type, int mode) {
	// é necessário garantir que antes não foram adicionados!
	ArrayList *l = st->studentList;
	Student *s;
	int i,n=arrayGetLength(l);
	for (i=0;i<n;i++) {
		s = arrayGet(l,i);
		if (s->type == type) {
			if (!mode) addStudent(st,s,0,1,0);
			else if (mode == 1) addStudent(st,s,0,1,1);
			else if (mode == 2) addStudent(st,s,1,1,1);
		}
	}
}

int removeWorstEdge (Store *st) {
	// remove a pior aresta!
	int min = -1;
	int iA,iB;
	int i,j,v;
	ArrayList *lst; Student *s;
	Graph *g = st->allocationGraph;
	for (i=1;i<g->nodes;i++) {
		for (j=0;j<i;j++) {
			if (!isRemovedEdge(st,i,j)) {
				v = getEdgeValue(g,i,j);
				if (min == -1 || (v < min && v) ) {
					iA = i; iB = j; min = v;
				}
			}
		}
	}
	if (min != -1) {
		removeEdge(st,iA,iB);
		lst = st->uces[iA]; j = 0;
		if (lst) j = arrayGetLength(lst);
		for (i=0;i<j;i++) {
			s = arrayGet(lst,i);
			if (s->uceB == iB) {
				removeStudent(st,s);
				addNonAllocated(st,s);
				i--; j--;
			}
		}
		return 1;
	} else {
		return 0;
	}
}
static void allocateList (Store *st, LinkedList *lst, int mode) {
	LinkedList *l = lst;
	Student *s;
	while (l) {
		s = getData(l);
		if (!mode) addStudent(st,s,0,1,0);
		else if (mode == 1) addStudent(st,s,0,1,1);
		else if (mode == 2) addStudent(st,s,1,1,1);
		l = l->next;
	}
}
void tryToMakeSomeoneHappy (Store *st) {
	// antes de adicionar é necessário remover da lista!
	LinkedList *l = st->unhappyStudents;
	LinkedList *lst = NULL;
	Student *s;
	while (l) {
		s = getData(l);		
		removeStudent(st,s);
		lst = insertElemHead(lst,s);
		l = l->next;
	}
	lst = sortList(lst,compareStudent,1);
	allocateList(st,lst,2);
}
void tryToAllocateNonAllocated (Store *st) {
	st->nonAllocatedStudents = sortList(st->nonAllocatedStudents,compareStudent,1);
	allocateList(st,st->nonAllocatedStudents,2);
}

void compoeAll (Store *st) {
	int i,n,m,j;
	ArrayList *lst; Student *s;
	for (i=0;i<st->nUCEs;i++) {
		lst = st->uces[i];
		if (lst) {
			arraySort(lst,compareStudent,1);
			n = getUCEgap(st,i);
			if (n < 0) {
				m = arrayGetLength(lst);
				for (j=m-1;j>=m+n;j--) {
					s = arrayGet(lst,j);
					removeStudent(st,s);
					addStudent(st,s,1,1,1);
				}
			}
		}
	}
}
